/*
 * Seldon -- open source prediction engine
 * =======================================
 *
 * Copyright 2011-2015 Seldon Technologies Ltd and Rummble Ltd (http://www.seldon.io/)
 *
 * ********************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * ********************************************************************************************
 */

/* Generated by Together */

package io.seldon.db.jdo;

import io.seldon.api.APIException;
import io.seldon.api.Constants;
import io.seldon.api.state.ClientConfigHandler;
import io.seldon.api.state.NewClientListener;
import io.seldon.db.jdbc.JDBCConnectionFactory;
import io.seldon.dbcp.DbcpInitialisedListener;
import io.seldon.dbcp.DbcpPoolHandler;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.sql.DataSource;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class JDOFactory implements NewClientListener, DbConfigHandler, DbcpInitialisedListener
{
	private static final Logger logger = Logger.getLogger( JDOFactory.class.getName() );
	public static final String DEFAULT_DB_JNDI_NAME = "ClientDB";

	private JDOPMRetriever pmRet = new JDOPMRetriever();
    private Map<String, PersistenceManagerFactory> factories = new ConcurrentHashMap<>();
    private Map<String,String>  clientJNDINames = new ConcurrentHashMap<>();
    private Map<String,String>  clientToDBName = new ConcurrentHashMap<>();

    @Autowired
    private Properties dataNucleusProperties;

	@Autowired
	private JDBCConnectionFactory jdbcConnectionFactory;

	@Autowired
	private DbcpPoolHandler dbcpPoolHandler;
	
	@Autowired
	private ClientConfigHandler clientConfigHandler;
	private static JDOFactory jdoFactory;
	private List<DbConfigListener> listeners = new ArrayList<>();


	@PostConstruct
	public void setup()
	{
		dbcpPoolHandler.addInitialisedListener(this);
	}
	
	@Override
	public void dbcpInitialised() {
		addDataSource("api", "api", DEFAULT_DB_JNDI_NAME);
		clientConfigHandler.addNewClientListener(this, true);
		jdoFactory = this; // should be removed at some point
	}
    
    public String getJNDIForClient(String client)
    {
    	return clientJNDINames.get(client);
    }

    private boolean addDataSource(String client,String dbName,String jndiName)
    {
    	DataSource ds = dbcpPoolHandler.get(jndiName);
		if (ds != null)
		{
			registerFactory(client,dbName,ds);
			jdbcConnectionFactory.addDataSource(client,ds,dbName);
			return true;
		}
		else
		{
			logger.error("Failed to get datasource for client "+client+" with jndiName="+jndiName);
			return false;
		}
    }
    
    private void registerFactory(String clientName, String databaseName, DataSource ds) {
    	Properties connectionProperties = (Properties) dataNucleusProperties.clone();
    	connectionProperties.put("javax.jdo.option.ConnectionFactory", ds);
		if (databaseName != null)
    		connectionProperties.setProperty("datanucleus.mapping.Catalog", databaseName);
    	logger.info("Adding PMF factory for client "+clientName+" with database "+databaseName);
        PersistenceManagerFactory factory = JDOHelper.getPersistenceManagerFactory(connectionProperties);
        factories.put(clientName, factory);
    }

    /**
	 * 	Return the singleton persistence manager factory instance.
	 */
	public synchronized PersistenceManagerFactory getPersistenceManagerFactory ()
	{
		if (factories.size() == 1)
			return factories.values().iterator().next();
		else
			return null;
	}

	public boolean isDefaultClient(String key)
	{
		return !factories.containsKey(key);
	}
    
	public PersistenceManager getPersistenceManager(String key)
    {
	    PersistenceManagerFactory pmf = factories.get(key);
	    if (pmf == null)
	    {
			throw new APIException(APIException.INTERNAL_DB_ERROR);
	    }
	    if (pmf != null)
	    {
	    	PersistenceManager pm = (PersistenceManager) pmRet.getPersistenceManager(key,pmf);
	    	if (!pm.currentTransaction().isActive())
	    		TransactionPeer.startReadOnlyTransaction(pm);
	    	return pm;
	    }
	    else
	    	return null;
    }
    
    public void cleanupPM()
    {
        pmRet.cleanup();
    }

	@Override
	public synchronized void clientAdded(String client, Map<String, String> initialConfig) {
		String jndiName = initialConfig.get("DB_JNDI_NAME");

		if(jndiName==null)
			jndiName = DEFAULT_DB_JNDI_NAME;

		String dbName = initialConfig.get("DB_NAME");

		if(dbName==null)
			dbName = client;

		logger.info("Adding client "+client+" JNDI="+jndiName+" dbName="+dbName);
		if (addDataSource(client, dbName, jndiName))
		{
			for(DbConfigListener listener: listeners)
				listener.dbConfigInitialised(client);
		}
	}

	@Override
	public void clientDeleted(String client) {
		logger.info("Removing PM factory for "+client);
		factories.get(client).close();
		factories.remove(client);
	}

	public static JDOFactory get(){
		return jdoFactory;
	}

	@Override
	public void addDbConfigListener(DbConfigListener listener) {
		listeners.add(listener);
	}


	
}
